using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
using System.IO;
using Quartz;
using Quartz.Impl;
using Infrastructure.Log;
using Microsoft.Extensions.Configuration;

namespace Infrastructure.Quartz
{
    /// <summary>
    /// Quartz任务调度
    /// </summary>
    public static class Scheduler
    {
        /// <summary>
        /// 开始调度任务
        /// </summary>
        public static async Task Start()
        {
            try
            {
                // 读取配置文件
                Dictionary<IJobDetail, ITrigger> jobDict = ReadConfig();

                await Start(jobDict);
            }
            catch (Exception ex)
            {
#pragma warning disable CS4014 // 由于此调用不会等待，因此在调用完成前将继续执行当前方法
                LogHelper.Error("Start scheduler job failed.", ex);
#pragma warning restore CS4014 // 由于此调用不会等待，因此在调用完成前将继续执行当前方法
            }
        }

        /// <summary>
        /// 开始调度任务
        /// </summary>
        /// <param name="jobDict">调度任务及触发器列表</param>
        /// <returns></returns>
        public static async Task Start(Dictionary<IJobDetail, ITrigger> jobDict)
        {
            try
            {
                IScheduler scheduler = new StdSchedulerFactory().GetScheduler().GetAwaiter().GetResult();

                // 添加监听器
                scheduler.ListenerManager.AddSchedulerListener(new GlobalSchedulerListener());

                // 添加任务
                foreach (var item in jobDict)
                {
                    await scheduler.ScheduleJob(item.Key, item.Value);
#pragma warning disable CS4014 // 由于此调用不会等待，因此在调用完成前将继续执行当前方法
                    LogHelper.Information($"Schedule job '{item.Key.Key.Name}' in group '{item.Key.Key.Group}' added.");
#pragma warning restore CS4014 // 由于此调用不会等待，因此在调用完成前将继续执行当前方法
                }

                await scheduler.Start();
            }
            catch (Exception ex)
            {
#pragma warning disable CS4014 // 由于此调用不会等待，因此在调用完成前将继续执行当前方法
                LogHelper.Error("Start scheduler job failed.", ex);
#pragma warning restore CS4014 // 由于此调用不会等待，因此在调用完成前将继续执行当前方法
            }
        }

        /// <summary>
        /// 停止调度任务
        /// </summary>
        public static async Task Shutdown()
        {
            try
            {
                IScheduler scheduler = new StdSchedulerFactory().GetScheduler().GetAwaiter().GetResult();

                await scheduler.Shutdown();
            }
            catch (Exception ex)
            {
#pragma warning disable CS4014 // 由于此调用不会等待，因此在调用完成前将继续执行当前方法
                LogHelper.Error("Shut down scheduler job failed.", ex);
#pragma warning restore CS4014 // 由于此调用不会等待，因此在调用完成前将继续执行当前方法
            }

        }

        /// <summary>
        /// 删除指定调度任务
        /// </summary>
        /// <param name="JobName">任务名称</param>
        /// <returns></returns>
        public static async Task DeleteJob(string JobName)
        {
            try
            {
                IScheduler scheduler = new StdSchedulerFactory().GetScheduler().GetAwaiter().GetResult();
                await scheduler.DeleteJob(JobKey.Create(JobName));
            }
            catch (Exception ex)
            {
#pragma warning disable CS4014 // 由于此调用不会等待，因此在调用完成前将继续执行当前方法
                LogHelper.Error("Shut down scheduler job failed.", ex);
#pragma warning restore CS4014 // 由于此调用不会等待，因此在调用完成前将继续执行当前方法
            }

        }

        /// <summary>
        /// 删除指定调度任务
        /// </summary>
        /// <param name="JobNames">任务名称集合</param>
        /// <returns></returns>
        public static async Task DeleteJob(List<string> JobNames)
        {
            try
            {
                List<JobKey> jobKeys = new List<JobKey>();
                for (int i = 0; i < JobNames.Count; i++)
                {
                    jobKeys.Add(JobKey.Create(JobNames[i]));
                }
                IScheduler scheduler = new StdSchedulerFactory().GetScheduler().GetAwaiter().GetResult();
                await scheduler.DeleteJobs(jobKeys);
            }
            catch (Exception ex)
            {
#pragma warning disable CS4014 // 由于此调用不会等待，因此在调用完成前将继续执行当前方法
                LogHelper.Error("Shut down scheduler job failed.", ex);
#pragma warning restore CS4014 // 由于此调用不会等待，因此在调用完成前将继续执行当前方法
            }

        }

        #region Private methods

        /// <summary>
        /// 读取调度任务的配置文件
        /// </summary>
        /// <returns>一个 Dictionary,其中保存Jobdetail及其对应的trigger</returns>
        private static Dictionary<IJobDetail, ITrigger> ReadConfig()
        {
            Dictionary<IJobDetail, ITrigger> resultDict = new Dictionary<IJobDetail, ITrigger>();
            List<ITrigger> triggers = new List<ITrigger>();

            // 读取配置文件
            var triggerSection = Config.GetSection("SchedulerJobTrigger", "scheduler.json");

            var triggerDefineSection = triggerSection.GetChildren();

            // 触发器设置
            foreach (var trigger in triggerDefineSection)
            {
                string name = trigger.GetValue<string>("Name");
                string group = trigger.GetValue<string>("Group");
                string type = trigger.GetValue<string>("Type");
                string misfire = trigger.GetValue<string>("Misfire");
                int? repeat = trigger.GetValue<int?>("Repeat");
                int? interval = trigger.GetValue<int?>("Interval");
                string cron = trigger.GetValue<string>("Cron");
                int? futureDate = trigger.GetValue<int?>("FutureDate");

                if (string.IsNullOrEmpty(name) || string.IsNullOrEmpty(group) ||
                    string.IsNullOrEmpty(type))
                {
                    continue;
                }

                TriggerBuilder triggerBuilder = TriggerBuilder
                    .Create()
                    .WithIdentity(name, group);

                // 设置启动时间
                if (futureDate.HasValue && futureDate.Value > 0)
                {
                    triggerBuilder = triggerBuilder
                        .StartAt(DateBuilder.FutureDate(futureDate.Value, IntervalUnit.Millisecond));
                }
                else
                {
                    triggerBuilder = triggerBuilder.StartNow();
                }

                // Corn触发器
                if (type.ToLower() == "cron")
                {
                    if (string.IsNullOrEmpty(cron))
                    {
                        continue;
                    }

                    // 未设置misfire
                    if (string.IsNullOrEmpty(misfire))
                    {
                        triggerBuilder = triggerBuilder.WithCronSchedule(cron);
                    }
                    else
                    {
                        // 设置misfire
                        void cronSchedule(CronScheduleBuilder cronScheduleBuilder)
                        {
                            switch (misfire.ToLower())
                            {
                                case "donothing":
                                    cronScheduleBuilder = cronScheduleBuilder
                                        .WithMisfireHandlingInstructionDoNothing();
                                    break;
                                case "fireandproceed":
                                    cronScheduleBuilder = cronScheduleBuilder
                                        .WithMisfireHandlingInstructionFireAndProceed();
                                    break;
                                case "ignoremisfires":
                                    cronScheduleBuilder = cronScheduleBuilder
                                        .WithMisfireHandlingInstructionIgnoreMisfires();
                                    break;
                                default:
                                    break;
                            }
                        }

                        triggerBuilder = triggerBuilder.WithCronSchedule(cron, cronSchedule);
                    }
                }
                else
                {
                    if (!repeat.HasValue || !interval.HasValue)
                    {
                        continue;
                    }

                    // 简单触发器
                    void simpleSchedule(SimpleScheduleBuilder simpleScheduleBuilder)
                    {
                        simpleScheduleBuilder = simpleScheduleBuilder
                            .WithInterval(TimeSpan.FromMilliseconds(interval.Value))
                            .WithRepeatCount(repeat.Value);

                        // 设置misfire
                        if (!string.IsNullOrEmpty(misfire))
                        {
                            switch (misfire.ToLower())
                            {
                                case "smartpolicy":
                                    break;
                                case "firenow":
                                    simpleScheduleBuilder = simpleScheduleBuilder
                                        .WithMisfireHandlingInstructionFireNow();
                                    break;
                                case "ignoremisfires":
                                    simpleScheduleBuilder = simpleScheduleBuilder
                                        .WithMisfireHandlingInstructionIgnoreMisfires();
                                    break;
                                case "nextwithexistingcount":
                                    simpleScheduleBuilder = simpleScheduleBuilder
                                        .WithMisfireHandlingInstructionNextWithExistingCount();
                                    break;
                                case "nextwithremainingcount":
                                    simpleScheduleBuilder = simpleScheduleBuilder
                                        .WithMisfireHandlingInstructionNextWithRemainingCount();
                                    break;
                                case "nowwithexistingcount":
                                    simpleScheduleBuilder = simpleScheduleBuilder
                                        .WithMisfireHandlingInstructionNowWithExistingCount();
                                    break;
                                case "nowwithremainingcount":
                                    simpleScheduleBuilder = simpleScheduleBuilder
                                        .WithMisfireHandlingInstructionNowWithRemainingCount();
                                    break;
                                default:
                                    break;
                            }
                        }
                    }

                    triggerBuilder = triggerBuilder
                        .WithSimpleSchedule(simpleSchedule);
                }

                triggers.Add(triggerBuilder.Build());
            }

            var jobSection = Config.GetSection("SchedulerJob", "scheduler.json");

            var jobDefineSection = jobSection.GetChildren();

            // 任务定义
            foreach (var item in jobDefineSection)
            {
                string name = item.GetValue<string>("Name");
                string group = item.GetValue<string>("Group");
                string execute = item.GetValue<string>("Execute");
                IConfigurationSection dataMap = item.GetSection("DataMap");
                string triggerName = item.GetValue<string>("Trigger");

                if (string.IsNullOrEmpty(name) || string.IsNullOrEmpty(group) ||
                    string.IsNullOrEmpty(execute) || string.IsNullOrEmpty(triggerName))
                {
                    continue;
                }

                // 解析任务类型
                Type jobType = GetJobType(execute);

                if (jobType == null)
                {
#pragma warning disable CS4014 // 由于此调用不会等待，因此在调用完成前将继续执行当前方法
                    LogHelper.Error($"Init scheduler job '{name}' failed,no such type or method '{execute}' " +
                        $"or property setting not correct.");
#pragma warning restore CS4014 // 由于此调用不会等待，因此在调用完成前将继续执行当前方法
                    continue;
                }

                JobBuilder jobBuilder = JobBuilder.Create(jobType);

                // 得到任务执行需要的参数
                if (dataMap != null)
                {
                    jobBuilder.UsingJobData(GetJobDataMap(dataMap));
                }

                // 匹配触发器
                ITrigger trigger = triggers.FirstOrDefault(p => p.Key.Name.ToLower() == triggerName.ToLower());

                if (trigger == null)
                {
#pragma warning disable CS4014 // 由于此调用不会等待，因此在调用完成前将继续执行当前方法
                    LogHelper.Error($"Init scheduler job '{name}' failed,no such trigger '{triggerName}'.");
#pragma warning restore CS4014 // 由于此调用不会等待，因此在调用完成前将继续执行当前方法
                    continue;
                }

                // 构建JobDetail
                IJobDetail job = jobBuilder
                    .WithIdentity(name, group)
                    .Build();

                resultDict.Add(job, trigger);
            }

            return resultDict;
        }

        /// <summary>
        /// 解析设置的JobType
        /// </summary>
        /// <param name="execute">需要解析的字符串</param>
        /// <returns>定义的JobType</returns>
        private static Type GetJobType(string execute)
        {
            string[] typeArray = execute.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);

            if (typeArray.Length < 2)
            {
                return null;
            }

            string path = PathHelper.GetBaseDirectory();
            path = Path.Combine(path, typeArray[1].Trim()) + ".dll";

            // 加载程序集
            Assembly assembly = Assembly.LoadFrom(path);

            if (assembly == null)
            {
                return null;
            }

            return assembly.GetType(typeArray[0].Trim(), false, true);
        }

        /// <summary>
        /// 解析配置文件配置的JobDataMap
        /// </summary>
        /// <param name="section">需要解析的IConfigurationSection</param>
        /// <returns>解析完成的JobDataMap</returns>
        private static JobDataMap GetJobDataMap(IConfigurationSection section)
        {
            JobDataMap jobDataMap = new JobDataMap();

            var childrenSection = section.GetChildren();

            if (childrenSection == null)
            {
                return jobDataMap;
            }

            foreach (var item in childrenSection)
            {
                jobDataMap.Put(item.Key, item.Value);
            }

            return jobDataMap;
        }

        #endregion

    }
}
